<template>
	<Teleport
		:disabled="appendTo==null"
		:to="appendTo"
	>
		<Transition
			:appear="appear"
			@enter="showFn"
			@after-enter="$emit('shown')"
			@leave="offHideOnClick"
			@after-leave="afterLeaveFn"
		>
			<div
				v-if="visible"
				ref="floatingRef"
				v-bind="$attrs"
				:class="floatingClass()"
				:style="floatingStyle()"
			>
				<div
					v-if="arrow"
					ref="arrowRef"
					class="pf-Floating_arrow"
					:style="arrowStyle()"
				/>
				<slot v-bind="slotScope()">
					<div
						class="pf-Floating_content"
						:style="contentStyle()"
					>{{content}}</div>
				</slot>
			</div>
		</Transition>
	</Teleport>
</template>

<script setup lang="ts">
import type {
	LimitProcessor,
} from './type';
import type {
	Placement,
	Side,
	ReferenceElement,
} from '@floating-ui/dom';
import {
	shallowRef,
	customRef,
	onMounted,
	onUnmounted,
	nextTick,
} from 'vue';
import {
	computePosition,
	flip,
	shift as shiftMiddleware,
	offset as offsetMiddleware,
	arrow as arrowMiddleware,
} from '@floating-ui/dom';
import maxSizeMiddleware from './maxSizeMiddleware';
import {getMainPlacement} from './utils';
import {operateDom,bindEvent} from '../../../util';
/**
 * 浮动元素组件
 * @author 唐国雄
 * @date 2022/11/23
 * 属性----------------------
 * :reference-ref		参考元素
 * :visible				显示状态
 * :auto-compute		自动计算气泡位置
 * :hide-on-click		点击隐藏
 * :content				提示内容
 * :placement			提示位置
 * :max-width			使用content时的最大宽度
 * :arrow				是否显示箭头
 * :shift				是否自动偏移
 * :offset				偏移
 * :padding				边距
 * :arrow-padding		箭头偏移边距
 * :interactive-border	可交互的外延
 * :append-to			dom插入位置
 * :theme				主题class
 * :appear				初始渲染动画
 * :animation			动画
 * 事件----------------------
 * @shown				显示动画完毕
 * @hidden				隐藏动画完毕
 * @hide-click			点击隐藏
 * 插槽----------------------
 * #default				浮动内容
 * 方法----------------------
 * isInside(e)			交互边界检查
 * computePoptip()		重新计算样式
 */
const emit=defineEmits<{
	(e:'shown'):void;
	(e:'hidden'):void;
	(e:'hide-click'):void;
}>();
const props=withDefaults(defineProps<{
	referenceRef:ReferenceElement;
	visible:boolean;
	autoCompute?:boolean;
	hideOnClick?:boolean|'toggle';
	content?:string;
	placement?:Placement;
	maxWidth?:number;
	arrow?:boolean;
	shift?:boolean;
	offset?:number;
	padding?:number;
	arrowPadding?:number;
	interactiveBorder?:number;
	appendTo?:string|HTMLElement;
	theme?:string;
	appear?:boolean;
	animation?:string;
}>(),{
	autoCompute:true,
	placement:'bottom',
	maxWidth:320,
	arrow:true,
	shift:true,
	offset:8,
	padding:5,
	arrowPadding:5,
	interactiveBorder:5,
	animation:'scale'
});
//region 获取一对数的引用
function getPairRef(initVal){
	return customRef((track,trigger) => {
		let innerX=initVal;
		let innerY=initVal;
		const obj={
			get value(){
				track();
				return [innerX,innerY];
			},
			set(x,y){
				if(innerX!==x||innerY!==y){
					innerX=x;
					innerY=y;
					trigger();
				}
			}
		};
		return {
			get(){
				return obj;
			},
			set(){},
		};
	}).value;
}
//endregion
const floatingRef=shallowRef<HTMLElement>(null);
const arrowRef=shallowRef<HTMLElement>(null);
const mainPlacement=shallowRef<Side>(getMainPlacement(props.placement));
const arrowPosition=getPairRef(0);
const floatingPosition=getPairRef(null);
const floatingLimits=getPairRef(null);
//region 浮动元素类名
const floatingClass=(() => {
	return `pf-Floating pf--placement-${mainPlacement.value} pf--animation-${props.animation}`+
		(props.theme?` ${props.theme}`:'');
});
//endregion
//region 浮动元素样式
const floatingStyle=(() => {
	const [x,y]=floatingPosition.value;
	if(x==null){
		return {
			visibility:'hidden',
		};
	}
	return {
		left:x+'px',
		top:y+'px',
	};
});
//endregion
//region 浮动内容样式
const contentStyle=() => {
	const style:Record<string,any>={};
	const [limitWidth,limitHeight]=floatingLimits.value;
	let {maxWidth}=props;
	if(limitWidth!=null){
		if(maxWidth==null||limitWidth<maxWidth){
			maxWidth=limitWidth;
		}
		style.maxHeight=limitHeight+'px';
	}
	if(maxWidth!=null){
		style.maxWidth=maxWidth+'px';
	}
	return style;
};
//endregion
//region 箭头样式
const arrowStyle=(() => {
	const [x,y]=arrowPosition.value;
	return {
		fontSize:props.offset*2+'px',
		transform:`translate(${x}px,${y}px)`,
	};
});
//endregion
//region 插槽作用域
const slotScope=(() => {
	const [limitWidth,limitHeight]=floatingLimits.value;
	return {
		computePoptip,
		limitWidth,
		limitHeight,
	};
});
//endregion
//region 计算气泡提示的样式数据
function computePoptip(limitFn?:LimitProcessor):Promise<boolean>{
	const {padding,shift}=props;
	const middleware=[
		offsetMiddleware(props.offset),
		flip({
			padding,
		}),
		shift
			?shiftMiddleware({
				padding,
			})
			:null,
		maxSizeMiddleware({
			shift,
			padding,
			apply(data){
				const limitWidth=Math.round(data.availableWidth);
				const limitHeight=Math.round(data.availableHeight);
				floatingLimits.set(limitWidth,limitHeight);
				if(limitFn){
					return limitFn(limitWidth,limitHeight);
				}
				return nextTick();
			}
		}),
		props.arrow?
			arrowMiddleware({
				element:arrowRef.value,
				padding:props.arrowPadding,
			})
			:null,
	].filter((m) => m!=null);
	return computePosition(props.referenceRef,floatingRef.value,{
		placement:props.placement,
		middleware,
	}).then(({x,y,placement,middlewareData}) => {
		mainPlacement.value=getMainPlacement(placement);
		floatingPosition.set(
			Math.round(x),
			Math.round(y),
		);
		if(middlewareData.arrow){
			const arrowData=middlewareData.arrow;
			arrowPosition.set(
				Math.round(arrowData.x||0),
				Math.round(arrowData.y||0),
			);
		}
		return true;
	});
}
//endregion
//region 点击隐藏
const [
	onHideOnClick,
	offHideOnClick,
]=bindEvent('mousedown',function(e:MouseEvent){
	if(props.hideOnClick===true){
		const target=e.target as Node;
		if(!floatingRef.value.contains(target)){
			const {referenceRef}=props;
			const ele=referenceRef instanceof Element?referenceRef:referenceRef.contextElement;
			if(!ele||!ele.contains(target)){
				emit('hide-click');
			}
		}
	}
},{
	passive:true,
});
//endregion
//region 显示计算等
const showFn=operateDom(() => {
	if(props.visible){
		props.autoCompute&&computePoptip();
		onHideOnClick();
	}
});
//endregion
//region 交互边界检查
function isInside({clientX,clientY}:MouseEvent){
	const {offset,interactiveBorder}=props;
	const mainPlacementVal=mainPlacement.value;
	let {top,bottom,left,right}=floatingRef.value.getBoundingClientRect();
	if(mainPlacementVal==='bottom'){
		top-=offset;
	}else if(mainPlacementVal==='top'){
		bottom+=offset;
	}else if(mainPlacementVal==='right'){
		left-=offset;
	}else{
		right+=offset;
	}
	const exceedsTop=top-clientY>interactiveBorder;
	const exceedsBottom=clientY-bottom>interactiveBorder;
	const exceedsLeft=left-clientX>interactiveBorder;
	const exceedsRight=clientX-right>interactiveBorder;
	return exceedsTop||exceedsBottom||exceedsLeft||exceedsRight;
}
//endregion
//region 过渡钩子
function afterLeaveFn(){
	if(!props.visible){
		emit('hidden');
	}
}
//endregion
onMounted(() => {
	if(!props.appear){
		showFn();
	}
});
onUnmounted(offHideOnClick);
//region 导出的内容
defineExpose({
	isInside,
	computePoptip,
});
//endregion
</script>

<script lang="ts">
import {platform} from '@floating-ui/dom';
platform.isRTL=() => false;
export default {
	inheritAttrs:false,
};
</script>